package deepbrain.simnetwork.loader.xml;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.Scanner;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import deepbrain.simnetwork.exception.SimNetworkException;
import deepbrain.simnetwork.loader.matrix.MatrixLoader;
import deepbrain.simnetwork.structure.Matrix;

/**
 * 
 * 
 * @author Li Yang
 * @create 2009-2-24
 */
public class XmlMatrixLoader<MatrixType extends Matrix<WeightType>,WeightType> implements MatrixLoader<MatrixType,WeightType> {

	private Document matrixXML;

	public XmlMatrixLoader(String filePath) throws SimNetworkException {
		try {
			InputStream inputStream = new FileInputStream(filePath);
			loadDocument(inputStream);
		} catch (FileNotFoundException e) {
			throw new SimNetworkException("Cannot Open File:" + filePath
					+ " When Loading Matrix", e);
		}
	}

	public XmlMatrixLoader(InputStream inputStream) throws SimNetworkException {
		loadDocument(inputStream);
	}

	private void loadDocument(InputStream inputStream)
			throws SimNetworkException {
		SAXReader reader = new SAXReader();
		try {
			matrixXML = reader.read(inputStream);
		} catch (DocumentException e) {
			throw new SimNetworkException(
					"Cannot Parse Matrix XML When Loading Matrix", e);
		}
	}

	@SuppressWarnings("unchecked")
	public MatrixType loadMatrix() throws SimNetworkException {

		if (matrixXML == null)
			return null;

		MatrixType matrix = null;
		Element matrixElement = (Element) matrixXML.selectSingleNode("//network/matrix");
		String matrixType = matrixElement.elementTextTrim("type");

		Class<MatrixType> matrixClass;
		try {
			matrixClass = (Class<MatrixType>) Class.forName(matrixType);
		} catch (ClassNotFoundException e) {
			throw new SimNetworkException("Cannot Find Matrix Class Type:"
					+ matrixType + " When Loading Matrix", e);
		}

		try {
			matrix = matrixClass.newInstance();
		} catch (Exception e) {
			throw new SimNetworkException(
					"Cannot Create Matrix Instance for Type:" + matrixType
							+ " When Loading Matrix", e);
		}

		String name = matrixElement.elementTextTrim("name");
		if (name != null)
			matrix.setName(name);

		int nrows, ncols;
		try {
			nrows = Integer.parseInt(matrixElement.elementTextTrim("nrows"));
			ncols = Integer.parseInt(matrixElement.elementTextTrim("ncols"));
		} catch (Exception e) {
			throw new SimNetworkException(
					"Cannot Get the Number of Rows and Cols When Loading Matrix",
					e);
		}

		String matrixData = matrixElement.elementTextTrim("weights");

		Object[][] matrixArray = loadMatrix(nrows, ncols, matrixData, matrix);
		matrix.setMatrix(matrixArray);

		return matrix;
	}

	protected Object[][] loadMatrix(int nrows,int ncols,String matrixData, Matrix<WeightType> matrixObject) throws SimNetworkException {
		Scanner scanner = new Scanner(matrixData);
		Object[][] matrix = new Object[nrows][];
		for(int y=0;y<nrows;y++) {
			Object[] row = new Object[ncols];
			for(int x=0;x<ncols;x++) {
				String weightString;
				try {
					weightString = scanner.next();
				} catch(Exception e) {
					throw new SimNetworkException("Too Early to Reach the End of Matrix File When Loading Matrix",e);
				}
				WeightType weight = matrixObject.translateToWeightTypeExceptionNest(weightString);
				row[x] = weight;
			}
			matrix[y] = row;
		}
		return matrix;
	}
}
